﻿using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using DimensionsHelper.LanguageService.Symbols;
using DimensionsHelper.LanguageService.Syntax;

namespace DimensionsHelper.LanguageService;

public partial class Preprocessor
{
    private readonly SourceSymbolTable _macroSymbolTable;

    /// <summary>
    ///     读取宏定义，即 #define 开头的表达式。
    /// </summary>
    /// <param name="defineKeywordToken">#define 关键字节点</param>
    /// <remarks>
    ///     可以是参数宏或者无参数的宏
    /// </remarks>
    private void ProcessMacro(SourceNode defineKeywordToken)
    {
        // #define
        int start = defineKeywordToken.Start;
        // macro_name
        if (!NextExpect(SyntaxKinds.Identifier, false, out SourceNode? name))
        {
            return;
        }

        Macro macro = NodeFactory.CreateMacro(
            _currentBlock,
            defineKeywordToken,
            NodeFactory.CreateSourceIdentifier(name),
            null);
        // 标记 Parser 跳过
        macro.IsParserSkipped = true;
        // 添加前一行的注释
        macro.SetLeadingComment(defineKeywordToken.GetLeadingComment());

        // 换行时，直接结束
        if (IsNextWrap())
        {
            FinishMacro();
            return;
        }

        bool hasParameterListError = false;
        bool hasParameterList = false;
        bool needComma = false;
        SourceNode token = ProcessNode();
        // 参数列表，只有在标识符后紧跟左括号时，才视为参数宏
        if (token.Start == name.End && token.IsNode(SyntaxKinds.LeftParenthesisToken))
        {
            hasParameterList = true;
            SourceNode leftParenthesisToken = token;
            SourceNode? rightParenthesisToken = null;
            List<MacroParameter> parameters = [];

            while (!_lexer.Eof())
            {
                token = ProcessNode();

                if (token.IsNode(SyntaxKinds.RightParenthesisToken))
                {
                    rightParenthesisToken = token;
                    break;
                }

                if (needComma)
                {
                    if (Expect(SyntaxKinds.CommaToken, true))
                    {
                        token = ProcessNode();
                    }

                    needComma = false;
                }

                // 参数名
                if (token.IsOneOfNode(SyntaxKinds.Identifier, SyntaxKinds.DotDotDotToken))
                {
                    MacroParameter parameter = NodeFactory.CreateMacroParameter(macro, token);
                    parameters.Add(FinishToken(parameter));
                    needComma = true;
                }
                else
                {
                    Raise(
                        DiagnosticDescriptors.UnexpectedToken,
                        _currentNode.Span,
                        "标识符");
                }

                // 如果后面换行，结束
                if (!IsNextWrap())
                {
                    continue;
                }

                hasParameterListError = true;
                break;
            }

            MacroParameterList paramList = NodeFactory.CreateMacroParameterList(macro, parameters);
            paramList.Update(leftParenthesisToken, rightParenthesisToken);
            FinishToken(paramList, leftParenthesisToken.Start, _currentNode.End);
            macro.Update(paramList);
        }

        if (hasParameterListError)
        {
            Raise(DiagnosticDescriptors.UnexpectedToken, _currentNode.Start, 1, ")");
            FinishMacro();
            return;
        }

        // 带有参数列表且换行时，结束
        if (hasParameterList)
        {
            if (IsNextWrap())
            {
                FinishMacro();
                return;
            }

            // 此时，lexer停留在右括号处，应当向前扫描一个Token
            token = ProcessNode();
        }

        MacroTokenPaster? currentPaster = null;

        // 定义列表
        do
        {
            int defStart = _currentNode.Start;
            // Paster1##
            // 扩展时
            // 1. 传入的参数先检查宏定义并展开已定义的宏。
            // 2. 传入空参数的位置忽略，例如：
            //     - #define TOKEN(A, B, C) A ## B ## C
            //       1> 当 TOKEN(,,) 时，展开成空表达式，结果忽略即可
            //       2> 当 TOKEN(A ,,) 时，忽略 B 和 C，展开结果是 A。

            if (token.IsNode(SyntaxKinds.Identifier) &&
                TryProcess(SyntaxKinds.HashHashToken, out SourceNode? hashHashToken))
            {
                // ## 后紧接标识符时，才是有效的 TokenPaster
                if (TryProcess(SyntaxKinds.Identifier, out SourceNode? pasterName2))
                {
                    currentPaster = NodeFactory.CreateMacroTokenPaster(macro, token, hashHashToken);
                    currentPaster.Update(pasterName2);
                    FinishToken(currentPaster, defStart);
                    macro.AddDefinition(currentPaster);
                }
                // 如果无效，检查 ## 是否和前面的标识符相连，如果相连，合并成一个标识符
                else
                {
                    if (hashHashToken.Start == token.End)
                    {
                        macro.AddDefinition(NodeConverter.CombineAsIdentifier([token, hashHashToken]));
                    }
                    else
                    {
                        macro.AddDefinition(token);
                        macro.AddDefinition(hashHashToken);
                    }
                }
            }
            // ## PasterName
            else if (token.IsNode(SyntaxKinds.HashHashToken)
                     && currentPaster != null
                     && TryProcess(SyntaxKinds.Identifier, out SourceNode? pasterName))
            {
                //  A ## B ## C
                //  1.   ##              ##
                //      /  \    --->    /  \
                //     A    B          A   ##
                //                        /  \
                //                       B    C
                Debug.Assert(currentPaster.Right.IsNode(SyntaxKinds.Identifier));
                int pasterStart = currentPaster.Left.Start;
                MacroTokenPaster rightPaster =
                    NodeFactory.CreateMacroTokenPaster(currentPaster, currentPaster.Right, token);
                rightPaster.Update(pasterName);
                FinishToken(rightPaster, currentPaster.Right.Start);
                FinishToken(currentPaster, pasterStart);
                currentPaster.Update(rightPaster);
                FinishToken(currentPaster, currentPaster.Left.Start);
                currentPaster = rightPaster;
            }
            // #Stringizer
            else if (token.IsNode(SyntaxKinds.HashToken)
                     && TryProcess(SyntaxKinds.Identifier, out SourceNode? stringizerName))
            {
                currentPaster = null;
                MacroStringizer stringizer = NodeFactory.CreateMacroStringizer(macro, token, stringizerName);
                FinishToken(stringizer, token.Start, stringizerName.End);

                // 检查stringizerName是否在参数列表中定义
                if (macro.ParameterList?.Find(stringizerName.Text) == null)
                {
                    Raise(DiagnosticDescriptors.IllStringizer, stringizer.Span);
                }
                else
                {
                    macro.AddDefinition(stringizer);
                }
            }
            else
            {
                currentPaster = null;
                macro.AddDefinition(token);
            }

            if (IsNextWrap())
            {
                break;
            }

            token = ProcessNode();
        } while (!_lexer.Eof());

        FinishMacro();
        return;

        void FinishMacro()
        {
            if (name.IsMacroPreDefined())
            {
                Raise(DiagnosticDescriptors.MacroNameIsPreDefined,
                    name.Span, name.Text);
            }
            else
            {
                FinishToken(macro, start, _currentNode.End,
                    _lexer.GetText(start, _currentNode.End - start));
                // 创建符号
                MacroSymbol symbol = new(macro);
                // 添加参数
                macro.ParameterList?.ForEach(p =>
                {
                    MacroParameterSymbol pSymbol = new(p.Name, p.GetLocation(),
                        symbol, p.IsVaArgs);
                    if (!symbol.TryAddParameter(pSymbol))
                    {
                        Raise(DiagnosticDescriptors.MacroParameterIsExist,
                            p, p.Name);
                    }
                });
                _macroSymbolTable.DeclareSymbol(symbol);
                // 将宏定义添加到原始文件或当前区间中，以便于按位置检索节点。
                (_currentBlock ?? _processedFile).AddNode(macro);
                _processedFile.AddMacro(macro);
            }
        }
    }


    /// <summary>
    ///     处理 #undef 预处理指令
    /// </summary>
    private void ProcessUndefDirective()
    {
        SourceNode nameNode = ProcessNode();
        if (nameNode.IsNode(SyntaxKinds.Identifier) && !nameNode.HasPrecedingLinebreak)
        {
            _macroSymbolTable.CancelDeclaration(nameNode.Text, nameNode.GetLocation());
        }
        else if (nameNode.HasPrecedingLinebreak)
        {
            Raise(DiagnosticDescriptors.UnexpectedToken, _lastNode.End, 1, "宏名");
        }
        else
        {
            Raise(DiagnosticDescriptors.UnexpectedToken, nameNode.Span, "宏名");
        }
    }


    // 因为不实际编译，#line 在此处没有实际作用，此处直接跳过即可。
    private void ProcessLineDirective()
    {
        _lexer.ReadToLinefeed();
    }


    // 在文件头部可以使用几种注释形式的指令
    // 1. ' <metadata />                                    - 将文件类型设置成 Metadata
    // 2. ' <include file="FILE_NAME"/>                     - 等同于 #include
    // 3. ' <reference file="FILE_NAME" name="MACRO_NAME"/> - 指示此文件引入到指定文件的指定宏位置
    // 4. ' <ignore-type-error />                           - 指示忽略此文件的类型错误


    [GeneratedRegex(
        """^\s*(?:(?<metadata>\<\s*metadata\s*\/\>)|(?<ignoreTypeError>\<\s*ignore\-type\-error\s*\/\>)|(?:\<include\s+file\s*\=\s*"(?<incFile>.*?)"\s*\/\>)|(?:\<reference\s+file\s*\=\s*"(?<refFile>.*?)"\s+name\s*=\s*"(?<refName>.*?)"\s*\/\>))""")]
    private static partial Regex GetDirectiveRegex();


    /// <summary>
    ///     尝试读取注释指令，如果不是有效的注释指令，返回<see langword="false" />
    /// </summary>
    /// <param name="commentText">注释文本</param>
    /// <param name="start">起始位置</param>
    /// <param name="startLine">起始行号</param>
    /// <param name="res">注释指令信息</param>
    private bool TryReadCommentDirective(string commentText, int start, int startLine,
        [MaybeNullWhen(false)] out CommentDirectiveInfo res)
    {
        res = null;
        Match match = GetDirectiveRegex().Match(commentText);
        if (!match.Success)
        {
            return false;
        }

        // ' <ignore-type-error/>
        if (!string.IsNullOrEmpty(match.Groups["ignoreTypeError"].Value))
        {
            res = new CommentDirectiveInfo(DirectiveKinds.IgnoreTypeError, null, null);
            return true;
        }

        // ' <metadata />
        string meta = match.Groups["metadata"].Value;

        if (!string.IsNullOrEmpty(meta))
        {
            res = new CommentDirectiveInfo(DirectiveKinds.MetadataFile, null, null);
            return true;
        }

        int startPos;
        int length;

        // <include ..
        Group incGroup = match.Groups["incFile"];
        string inc = incGroup.Value;

        if (!string.IsNullOrEmpty(inc))
        {
            startPos = start + incGroup.Index;
            length = inc.Length;
            res = new CommentDirectiveInfo(
                DirectiveKinds.Include,
                ProcessString(null, startPos, length),
                null);
            return true;
        }

        // <reference
        Group refFileGroup = match.Groups["refFile"];
        Group refNameGroup = match.Groups["refName"];

        if (string.IsNullOrEmpty(refFileGroup.Value) ||
            string.IsNullOrEmpty(refNameGroup.Value))
        {
            return false;
        }

        startPos = start + refFileGroup.Index;
        length = refFileGroup.Value.Length;
        SourceString refFileStr = ProcessString(null, startPos, length);
        refFileStr.UpdateLine(startLine, startLine);

        startPos = start + incGroup.Index;
        length = refNameGroup.Value.Length;
        SourceString refNameStr = ProcessString(null, startPos, length);
        refNameStr.UpdateLine(startLine, startLine);

        res = new CommentDirectiveInfo(DirectiveKinds.Reference, refFileStr, refNameStr);
        return true;
    }


    /// <summary>
    ///     处理文件头部的注释指令
    /// </summary>
    private void ProcessCommentDirective()
    {
        foreach (SyntaxComment comment in _lexer.GetComments())
        {
            if (comment is not
                {
                    CommentKind: SyntaxCommentKinds.LineComment,
                    IsEmpty: false
                } ||
                !TryReadCommentDirective(comment.Lines[0].Text, comment.Start, comment.StartLine,
                    out CommentDirectiveInfo? info))
            {
                continue;
            }

            Directive directive = info.FileName != null
                ? NodeFactory.CreateDirective(info.FileName, info.ReferenceName)
                : NodeFactory.CreateDirective(info.Kind);

            directive.Update(comment.Start, comment.End, comment.Lines[0].Text);
            _directives.Add(directive);

            // <include ..
            if (info is { Kind: DirectiveKinds.Include, FileName: not null })
            {
                directive.SourceFile = ProcessIncludeFileName(info.FileName,
                    info.FileName.Text);
            }
        }
    }


    /// <summary>
    ///     处理 #error 指令
    /// </summary>
    /// <param name="errorKeywordToken">#error 关键字</param>
    private void ProcessErrorDirective(SourceNode errorKeywordToken)
    {
        int end = errorKeywordToken.Start + 1;
        string errorText = string.Empty;

        if (!IsNextWrap())
        {
            SourceNode errString = _lexer.ReadToLinefeed();
            errorText = errString.Text;
            end = errString.End;
        }

        Raise(DiagnosticDescriptors.ErrorDirective,
            errorKeywordToken.Start, end - errorKeywordToken.Start, errorText);
    }


    private string ResolveIncludeFileName(List<SourceNode> nodes, out List<SourceTextFile> references)
    {
        string res = string.Empty;
        references = [];
        switch (nodes.Count)
        {
            case 0:
                return res;
            // 单个节点
            case 1:
            {
                SourceNode n = nodes[0];
                if (n.IsNode(SyntaxKinds.String))
                {
                    res = n.Text;
                }
                else if (n.IsNode(SyntaxKinds.Identifier))
                {
                    SourceIdentifier? placeholder = n as SourceIdentifier;
                    // 展开已定义的宏
                    if (_macroSymbolTable.TryGetSymbol(n.Text, out ISymbol? macroSymbol))
                    {
                        MacroExpansion expansion = ExpandNormalMacro(n, (MacroSymbol)macroSymbol);
                        // 如果已展开的宏是单个标识符，保存为标识符
                        if (expansion is [{ IsIdentifier: true }])
                        {
                            placeholder = expansion[0] as SourceIdentifier;
                        }
                        // 如果是字符字符串，查找对应的文件
                        else
                        {
                            if (expansion is [{ IsString: true }])
                            {
                                res = _source.GetFullPath(expansion[0].Text);
                            }

                            placeholder = null;
                        }
                    }

                    // 查找已有的占位符引用定义
                    if (placeholder == null)
                    {
                        return res;
                    }

                    ImmutableArray<(string, SourceTextFile)> refSources =
                        _source.Manager.GetReferences(placeholder.Text);
                    references.AddRange(refSources.Select(pair => pair.Item2));
                }

                break;
            }
        }

        return res;
    }


    /// <summary>
    ///     处理文件名节点并检索预处理文件
    /// </summary>
    /// <param name="nameNode">文件名节点</param>
    /// <param name="resolvedPath">文件的全限定名</param>
    private SourceTextFile? ProcessIncludeFileName(SourceNode nameNode, string resolvedPath)
    {
        if (!Path.IsPathRooted(resolvedPath))
        {
            resolvedPath = Path.GetFullPath(resolvedPath, _source.Directory);
        }

        if (string.IsNullOrEmpty(resolvedPath) ||
            !_source.Manager.TryLoadSourceFile(resolvedPath, out SourceTextFile? src))
        {
            Raise(DiagnosticDescriptors.IncludePathFailure, nameNode, nameNode.Text);
            return null;
        }

        src.Preprocessed ??= Process(src, _macroSymbolTable);
        // 引入宏定义
        _macroSymbolTable.Join(src.Preprocessed.MacroSymbols);
        return src;
    }


    /// <summary>
    ///     处理 #include 指令
    /// </summary>
    /// <param name="includeKeywordToken">#include 关键字</param>
    private void ProcessInclude(SourceNode includeKeywordToken)
    {
        int start = _currentNode.Start;
        int end = _currentNode.End;

        List<SourceNode> pathNodes = [];
        while (!_lexer.Eof() && !IsNextWrap())
        {
            pathNodes.Add(ProcessNode());
        }

        SourceNode pathNode = SourceNode.Default;

        if (pathNodes.Count > 0)
        {
            end = pathNodes[^1].End;

            if (pathNodes.Count == 1)
            {
                pathNode = pathNodes[0];
            }
        }

        FileInclude inc = NodeFactory.CreateFileInclude(null, includeKeywordToken, pathNode);

        // 如果路径节点是单个标识符，检查文件引用关系
        if (pathNodes is not [{ IsIdentifier: true }])
        {
            string fullPath = ResolveIncludeFileName(pathNodes, out List<SourceTextFile>? references);
            inc.ResolvedFilePath = fullPath;
            inc.Update(ProcessIncludeFileName(pathNode, fullPath));
            // 对于占位符使用引用的文件，只将当前的符号表加入到各个引用文件的宏符号表中，
            // 不在引用文件处理完成后加入新的符号。
            foreach (SourceTextFile? reference in references)
            {
                reference.Preprocessed ??= Process(reference, _macroSymbolTable);
            }
        }

        if (!_processedFile.AddIncludeDirective(FinishToken(inc, start, end - start)))
        {
            Raise(DiagnosticDescriptors.IncludeRecursion, inc);
        }
    }


    private record CommentDirectiveInfo(
        DirectiveKinds Kind,
        SourceNode? FileName,
        SourceNode? ReferenceName);
}